Skip to content

Conversation

@gmazzo
Copy link
Contributor

@gmazzo gmazzo commented Aug 20, 2025

What are you trying to accomplish?

Introduces Gradle Wrapper bump support for gradle manager

Closes #2223 (issue open since 2018)

Smoke Tests PR: dependabot/smoke-tests#325
GitHub Docs PR: github/docs#39954

Anything you want to highlight for special attention from reviewers?

Following the same pattern used by gradle/libs.version.toml file, we'll only look (and support) for a gradle/wrapper/gradle-wrapper.properties in the root of the project.

distributionSha256Sum is also supported, and it will only be updated if the original properties file has the property.

Note

To minimize the risk of the feature and the impact on other smoke tests, I've introduced a new gradle_wrapper_updater feature flag for this change.

JAR binary and script files support

Updating other the gradle-wrapper.jar or the companion shell scripts is done through the new WrapperUpdater helper by leveraging the existing LockfileUpdater.
Most of its code was extracted into a new GradleUpdaterBase class, which has two abstract methods:

  • target_file? to tell the given file is meant to be updated by it
  • command_args to collaborate with the final gradle command run

Now both LockfileUpdater and the new WrapperUpdater inherit from GradleUpdaterBase.

Note

The update will not attempt to use any configuration for the wrapper task in the main build script; many of the repos will be broken because they rely on other files or included builds that will take a long time to complete. We limit to copying the wrapper-related files and running a clean, empty build.

How will you know you've accomplished your goal?

Running against a sample repo:

bin/docker-dev-shell gradle --rebuild
$ bin/dry-run.rb gradle gmazzo/gradle-codeowners-plugin

produces the desired diff:

 => bump gradle-wrapper from 8.14.3 to 9.0.0

    ± gradle/wrapper/gradle-wrapper.properties
    ~~~
    --- /tmp/original20250820-3941-jbt37h       2025-08-20 12:30:57.699516752 +0000
    +++ /tmp/updated20250820-3941-peq4i4        2025-08-20 12:30:57.700516752 +0000
    @@ -1,7 +1,7 @@
     distributionBase=GRADLE_USER_HOME
     distributionPath=wrapper/dists
    -distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531
    -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
    +distributionSha256Sum=8fad3d78296ca518113f3d29016617c7f9367dc005f932bd9d93bf45ba46072b
    +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
     networkTimeout=10000
     validateDistributionUrl=true
     zipStoreBase=GRADLE_USER_HOME
    ~~~
    3 insertions (+), 3 deletions (-)

Checklist

  • I have run the complete test suite to ensure all tests and linters pass.
  • I have thoroughly tested my code changes to ensure they work as expected, including adding additional tests for new functionality.
  • I have written clear and descriptive commit messages.
  • I have provided a detailed description of the changes in the pull request, including the problem it addresses, how it fixes the problem, and any relevant details about the implementation.
  • I have ensured that the code is well-documented and easy to understand.

Important

Do not squash this PR, so we won't the history of lockfile_updater.rb being renamed to gradle_updater_base.rb and to attribute the original code to its autor.

@github-actions github-actions bot added the L: java:gradle Maven packages via Gradle label Aug 20, 2025
@gmazzo gmazzo force-pushed the gradle-wrapper-support branch 4 times, most recently from 1d1fb9a to bca9d9d Compare August 20, 2025 14:23
@gmazzo gmazzo force-pushed the gradle-wrapper-support branch 3 times, most recently from 90b0de3 to ba37efc Compare August 20, 2025 15:14
@gmazzo gmazzo marked this pull request as ready for review August 20, 2025 15:30
@gmazzo gmazzo requested a review from a team as a code owner August 20, 2025 15:30
module Distributions
extend T::Sig

DISTRIBUTIONS_URL = "https://services.gradle.org"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we detect this from the gradle-wrapper.properties?

In my current configuration, for example, this is pointing to a custom proxy. Example

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https://example.com/gradle-distributions/gradle-9.0.0-bin.zip
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionBuildTime=20250731163512+0000

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty much intentional, because:

  • The PR is aiming to support the 95% of the cases (if not more) where people use a standard Gradle distribution
  • Even if I manage to infer the base URL for a non standard distribution, there won't be any guarantee that in that same server will exist an equivalent listing endpoint https://services.gradle.org/versions/all, or it will honor it's contact

Therefore the extra complexity to support this edge case won't pay off, at least in the scope of this PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's understandable, but one of the core features of dependabot is the support for private registries, we may need to take that into account

Copy link
Contributor

@yeikel yeikel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for making this change. It would be a great addition!

A few additional points regarding the review:

A key aspect of full Gradle wrapper support is regenerating the wrapper scripts (like gradlew and related files). If only the property file is updated and the scripts aren’t regenerated, it could cause issues.

In my view, a proper Gradle Wrapper upgrade should:

  • Update the distribution property file
  • Use the native Gradle binary to regenerate the wrapper scripts
  • Commit both changes

For reference, see how the Update Gradle Wrapper Action handles this process.

As a side note, considering recent moves to separate ecosystems (such as npm and bum), it might be worth discussing if this should become its own ecosystem. I understand that gradle and the wrapper are related, but the structure and goal are different. Ultimately, however, it will be matter of opinion

I’ll leave that decision to the Dependabot team, as there are both advantages and disadvantages.

@gmazzo
Copy link
Contributor Author

gmazzo commented Aug 20, 2025

Thank you for making this change. It would be a great addition!

A few additional points regarding the review:

A key aspect of full Gradle wrapper support is regenerating the wrapper scripts (like gradlew and related files). If only the property file is updated and the scripts aren’t regenerated, it could cause issues.

In my view, a proper Gradle Wrapper upgrade should:

  • Update the distribution property file
  • Use the native Gradle binary to regenerate the wrapper scripts
  • Commit both changes

For reference, see how the Update Gradle Wrapper Action handles this process.

As a side note, considering recent moves to separate ecosystems (such as npm and bum), it might be worth discussing if this should become its own ecosystem. I understand that gradle and the wrapper are related, but the structure and goal are different. Ultimately, however, it will be matter of opinion

I’ll leave that decision to the Dependabot team, as there are both advantages and disadvantages.

Thanks for the review @yeikel !

Let's wait for the maintainers feedback, but IMHO updating the whole set of scripts as that action does is just against the spirit of the tool itself. Not to mention it will be a technical challenge (or a blocker) to implement properly.

I dig into this tool a few days ago, so I'm not an expert at all. But based on what I get from experience, it works with a "token replacement" approach.

Implementing a proper script updating will require either to run Gradle or to do reverse engineering to the wrapper to mimic its behavior. That's just not the way this tool seems to work.

If a full update is required/desired, there are others tools for that or you can just do it manually yourself.

There better "Gradle-native" approaches to update dependencies that does not has the flaws the Dependabot has. This tool just provides a static fast approach that fits in the 90% of the cases (again or even more). The wrapper updater just falls under the same pattern IMHO

If only the property file is updated and the scripts aren’t regenerated, it could cause issues.

In my experience, this has never happened. The wrapper API has been pretty stable from Gradle earlier versions

it might be worth discussing if this should become its own ecosystem.

Well, IMHO I disagree. That Gradle has a different set of things to update, does not implies they are different ecosystems. If your thoughts are based on the Renovate approach, I'm not sure why it was implemented like that, but high-level, Gradle is just one. Its wrapper is just another kind of updatable component.

@yeikel
Copy link
Contributor

yeikel commented Aug 21, 2025

Let's wait for the maintainers feedback, but IMHO updating the whole set of scripts as that action does is just against the spirit of the tool itself. Not to mention it will be a technical challenge (or a blocker) to implement properly.

I dig into this tool a few days ago, so I'm not an expert at all. But based on what I get from experience, it works with a "token replacement" approach.

If a full update is required/desired, there are others tools for that or you can just do it manually yourself.

There better "Gradle-native" approaches to update dependencies that does not has the flaws the Dependabot has. This tool just provides a static fast approach that fits in the 90% of the cases (again or even more). The wrapper updater just falls under the same pattern IMHO

If only the property file is updated and the scripts aren’t regenerated, it could cause issues.

In my experience, this has never happened. The wrapper API has been pretty stable from Gradle earlier versions

I appreciate your perspective, though I see things a bit differently. Dependabot aims to align closely with native tools, which is why there’s a growing focus on integrating with each ecosystem’s native tooling, for example, regenerating lock files or running mvn commands to get additional insights from maven directly.

The wrapper-related files are part of the wrapper distribution, which is why the official documentation recommends using Gradle to update the wrapper.

Dependabot should aim to create pull requests that are ready for immediate merging, without requiring manual intervention to finalize them. In this case, that'd would include all the related files to the wrapper

Implementing a proper script updating will require either to run Gradle or to do reverse engineering to the wrapper to mimic its behavior. That's just not the way this tool seems to work.

Yes, I'd think we should run gradle to execute the upgrade after we identify the version we are trying to upgrade to.

From the docs:

       ./gradlew wrapper --gradle-version {gradleVersion}

In my experience, this has never happened. The wrapper API has been pretty stable from Gradle earlier versions

Your experience may be limited to specific cases, but Gradle often makes changes for important reasons, such as updating the binary, improving Java version detection, or other enhancements. These updates go beyond simply keeping the wrapper functional; they are all part of the same distribution, and ensuring alignment across these components is essential. Keeping them out of sync may introduce issues that are hard to troubleshoot

That said, the Dependabot team may welcome this initial version and use it to gather feedback. I would be among the first to raise an issue to ensure these files are generated for a more complete release. It doesn’t need to be included in the first iteration; we can release now and improve it over time. That's also a valid strategy

it might be worth discussing if this should become its own ecosystem.

Well, IMHO I disagree. That Gradle has a different set of things to update, does not implies they are different ecosystems. If your thoughts are based on the Renovate approach, I'm not sure why it was implemented like that, but high-level, Gradle is just one. Its wrapper is just another kind of updatable component.

Although this is a subjective view, I believe there is a meaningful separation between the Gradle wrapper and application library dependencies. The wrapper and libraries follow different lifecycles: their versions are managed and detected in distinct ways, updated through separate processes, and the wrapper does not interact with registries or dependency resolution like application libraries do.

Still, both are important parts of the Gradle ecosystem as you mentioned, and there are reasonable arguments for treating them together or separately depending on the situation. Ultimately, there are advantages and disadvantages to both approaches, and your perspective is valid as well

@gmazzo
Copy link
Contributor Author

gmazzo commented Aug 21, 2025

regenerating lock files or running mvn commands to get additional insights from the Maven directly.

Thanks for pointing that out, @yeikel. I just noted there are effective piece of code that run native tools, like mvn

I was originally reluctant to propose a solution like that, because running gradle requires having java installed as well. Which, at this point I'd assume it does if it's running maven.

Now I think a complementary step may be added to use ./gradlew wrapper task instead, falling back to this original approach if it fails.

Let me see what I can do, or it can be done in a follow-up PR too. I'd like to get feedback from the maintainers about this as well.

@yeikel
Copy link
Contributor

yeikel commented Aug 21, 2025

I was originally reluctant to propose a solution like that, because running gradle requires having java installed as well. Which, at this point I'd assume it does if it's running maven.
Now I think a complementary step may be added to use ./gradlew wrapper task instead, falling back to this original approach if it fails.

The good news is that both Java and Gradle come pre-installed in the Gradle containers, so no fallback is required.

See

RUN apt-get update && apt-get install -y --no-install-recommends \
openjdk-21-jdk \
ca-certificates-java \
wget \
&& rm -rf /var/lib/apt/lists/*
# Install Gradle
ENV GRADLE_HOME=/opt/gradle
ENV GRADLE_VERSION=8.14.2
ARG GRADLE_DOWNLOAD_SHA256=7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999
RUN set -o errexit -o nounset \
&& echo "Downloading Gradle" \
&& wget --no-verbose --output-document=gradle.zip "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" \
\
&& echo "Checking Gradle download hash" \
&& echo "${GRADLE_DOWNLOAD_SHA256} *gradle.zip" | sha256sum -c - \
\
&& echo "Installing Gradle" \
&& unzip gradle.zip \
&& rm gradle.zip \
&& mv "gradle-${GRADLE_VERSION}" "${GRADLE_HOME}/" \
&& ln -s "${GRADLE_HOME}/bin/gradle" /usr/bin/gradle

@gmazzo gmazzo force-pushed the gradle-wrapper-support branch from ba37efc to 47f7faa Compare August 22, 2025 09:21
@gmazzo
Copy link
Contributor Author

gmazzo commented Aug 22, 2025

Hi @yeikel, I managed to get that to work, but in a complementary PR #12908 (since it involves more changes and a refactor of the gradle.lockfile feature)

Let's say if this one gets reviewed by Dependabot team soon 🙏

@yeikel
Copy link
Contributor

yeikel commented Aug 23, 2025

Hi @yeikel, I managed to get that to work, but in a complementary PR #12908 (since it involves more changes and a refactor of the gradle.lockfile feature)

Let's say if this one gets reviewed by Dependabot team soon 🙏

Awesome, thank you!

I think that before this is reviewed, you should make sure that all the tests are passing as that'll be the first feedback you'll get 🙏

As of right now, the e2e smoke tests are failing

@gmazzo
Copy link
Contributor Author

gmazzo commented Aug 24, 2025

you should make sure that all the tests are passing as that'll be the first feedback you'll get 🙏

I coudn't find documentation on how to work with the smoke tests. They are in a different repo. I have a PR for them as well, but changing SMOKE_TEST_BRANCH does not feel correct either 🤔

Edit:
I just made changes to the smoke.yml workflow

@gmazzo gmazzo force-pushed the gradle-wrapper-support branch 4 times, most recently from 06fb136 to 05127fe Compare August 25, 2025 14:10
@gmazzo gmazzo force-pushed the gradle-wrapper-support branch from cec4148 to 06d21c8 Compare October 15, 2025 12:28
@kbukum1 kbukum1 requested a review from Copilot October 28, 2025 17:43
def versions
if Distributions.distribution_requirements?(dependency.requirements)
return DistributionsFinder.available_versions
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few issues here that need to be addressed:

  1. Consolidate version fetching logic: Package::PackageDetailsFetcher is designed to fetch package versions, so the distribution fetching should be moved into PackageDetailsFetcher as well. This way we have a single method to fetch all versions.

  2. Add feature flag check: We need to check Experiments.enabled?(:gradle_wrapper_updater) before fetching distributions, just like we did in the file parsing logic.

  3. Include released_at field: The current format is missing the released_at field, which means cooldown functionality won't work for Gradle wrapper updates.

Current format:

{
  version: version,
  source_url: source_url
}

Should match the format used by PackageDetailsFetcher:

{
  version: version,
  released_at: released_at,
  source_url: source_url
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Point 1 seems to be the most challenging, let me see what I do

Copy link
Contributor Author

@gmazzo gmazzo Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, @kbukum1, I think I covered all the feedback. I still kept some of the new logic isolated out from PackageDetailsFetcher (in a DistributionsFetcher sibling) but it's definitely integrated into it now.

I think this is better because it won't unnecessary pollute the PackageDetailsFetcher (which is more Maven related) with this, but let me know what you think ;)

PS: the new gradle-wrapper smoke test run is here in a companion PR on my fork repo

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@kbukum1 kbukum1 moved this from Ready to In Progress in Dependabot Oct 29, 2025
@gmazzo gmazzo force-pushed the gradle-wrapper-support branch from 41b689b to 56e59ed Compare October 30, 2025 19:56
@gmazzo gmazzo force-pushed the gradle-wrapper-support branch from 56e59ed to d4259fb Compare October 30, 2025 20:11
Comment on lines +106 to +123
updated_files = T.let([], T::Array[Dependabot::DependencyFile])
buildfiles_processed.each do |_, buildfile|
if Dependabot::Experiments.enabled?(:gradle_lockfile_updater)
lockfile_updater = LockfileUpdater.new(dependency_files: files)
updated_files += lockfile_updater.update_lockfiles(buildfile)
end
if Dependabot::Experiments.enabled?(:gradle_wrapper_updater)
wrapper_updater = WrapperUpdater.new(dependency_files: files, dependency: dependency)
updated_files += wrapper_updater.update_files(buildfile)
end
end

updated_files.each do |file|
existing_file = files.find { |f| f.name == file.name && f.directory == file.directory }
if existing_file.nil?
files << file
else
files[T.must(files.index(existing_file))] = file
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be now the most "controversial" refactor, so let me explain a bit why.

Before you were just calling LockfileUpdater.update_lockfiles on the buildfile and collecting its affecting files. That's OK.

Now (and to avoid duplicating the logic, but I'd be fine if that's what you want/need) I did:

  • Created a updated_files array to collect files from both LockfileUpdater and the new WrapperUpdater (they will only run if their experiments are enabled)
  • Then run the last piece of logic of "affected files" for the resulting collection. lockfiles.each do |lockfile| was renamed to updated_files.each do |file| just for semantic coherence.
  • You may also noted the buildfiles_processed map:
    • This was needed because the actual logic will be run for every requirement in the dependency.
    • For Gradle, we never required more than just, just the version bump
    • But in order to fully support Gradle Distribution, I modeled the dependency with two requirements:
      • The version
      • And the distributionSHA
    • The thing is, without change, the first run (for the version requirement) of the native tool (gradle wrapper) will update both of time, in the gradle.properties file.
    • Because of this, a further second run will fail, because no files will be updated. Also a second run will be unnecessary and just a wast of time and resources for the tool.
    • So to overcome this, is what I "keep track" of build files were I run the native tools already, so I won't run them again.

package_releases << {
version: Gradle::Version.new(version),
released_at: release_date_info.none? ? nil : (release_date_info[version]&.fetch(:release_date) || nil),
released_at: info[:released_at] || release_date_info[version]&.fetch(:release_date),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change should be equivalent to the code before, except that now if released_at is set directly in info (only applies for distributions) it will take priority. This was to avoid duplicating the release_date_info structure from Maven packages

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L: java:gradle Maven packages via Gradle

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

Add support for updating gradle wrapper

4 participants